#include "sysconfig.h"
#include "zfile.h"

/* based on http://libxad.cvs.sourceforge.net/libxad/libxad/portable/clients/ by Dirk Stoecker */

#define XADERR_ILLEGALDATA 1
#define XADERR_DECRUNCH 2
#define XADERR_NOMEMORY 3

struct rledata
{
    uint rledatav;
};

struct fout
{
    struct zfile* zf;
    int xio_BitNum;
    int xio_BitBuf;
    int err;
};

static void putrle(byte data, struct zfile* out, struct rledata* rled)
{
    int num;
    uint a;

    if (!rled)
    {
        zfile_putc(data, out);
        return;
    }
    a = rled->rledatav;
    if (a & 0x100) /* was RLE mode */
    {
        if (!data || (data == 1 && (a & 0x80000000)))
        {
            a = 0x90;
            num = 1;
        }
        else
        {
            a &= 0xFF;
            num = data - 1;
        }
    }
    else if (data == 0x90)
    {
        num = 0;
        a |= 0x100;
    }
    else
    {
        num = 1;
        a = data;
    }
    rled->rledatav = a;
    while (num--)
        zfile_putc(a, out);
}

static uint xadIOGetBitsLow(struct fout* io, byte bits)
{
    uint x;

    io->err = 0;
    while (io->xio_BitNum < bits)
    {
        int v = zfile_getc(io->zf);
        if (v == -1)
        {
            io->err = 1;
            return 0;
        }
        io->xio_BitBuf |= v << io->xio_BitNum;
        io->xio_BitNum += 8;
    }
    x = io->xio_BitBuf & ((1 << bits) - 1);
    io->xio_BitBuf >>= bits;
    io->xio_BitNum -= bits;
    return x;
}

#define ARCSQSPEOF 256                  /* special endfile token */
#define ARCSQNUMVALS 257                /* 256 data values plus SPEOF */

static int ARCunsqueeze(struct zfile* in, struct zfile* out, struct rledata* rled)
{
    int err = 0;
    int i, numnodes;
    short* node;
    struct fout io;

    io.zf = in;
    io.xio_BitBuf = 0;
    io.xio_BitNum = 0;
    io.err = 0;

    if ((node = (short*)xcalloc(short, 2 * ARCSQNUMVALS)))
    {
        numnodes = xadIOGetBitsLow(&io, 16);

        if (numnodes < 0 || numnodes >= ARCSQNUMVALS)
            err = XADERR_DECRUNCH;
        else
        {
            /* initialize for possible empty tree (SPEOF only) */
            node[0] = node[1] = -(ARCSQSPEOF + 1);

            numnodes *= 2;
            i = 0;
            while (i < numnodes && !io.err)       /* get decoding tree from file */
            {
                node[i++] = xadIOGetBitsLow(&io, 16);
                node[i++] = xadIOGetBitsLow(&io, 16);
            }

            do
            {
                /* follow bit stream in tree to a leaf */
                i = 0;
                while (i >= 0 && !io.err)
                    i = node[2 * i + xadIOGetBitsLow(&io, 1)];

                i = -(i + 1); /* decode fake node index to original data value */

                if (i != ARCSQSPEOF)
                    putrle(i, out, rled);
            }
            while (i != ARCSQSPEOF);
        }
        free(node);
    }
    else
        err = XADERR_NOMEMORY;

    return err;
}

#define UCOMPMAXCODE(n) (((uint)1 << (n)) - 1)
#define UCOMPBITS 16
#define UCOMPSTACKSIZE 8000
#define UCOMPFIRST 257                  /* first free entry */
#define UCOMPCLEAR 256                  /* table clear output code */
#define UCOMPINIT_BITS 9                /* initial number of bits/code */
#define UCOMPBIT_MASK 0x1f
#define UCOMPBLOCK_MASK 0x80

struct UCompData
{
    short clear_flg;
    ushort n_bits;                   /* number of bits/code */
    ushort maxbits;                  /* user settable max # bits/code */
    uint maxcode;                  /* maximum code, given n_bits */
    uint maxmaxcode;
    int free_ent;
    int offset;
    int size;
    ushort* tab_prefixof;
    byte* tab_suffixof;
    byte stack[UCOMPSTACKSIZE];
    byte buf[UCOMPBITS];
    int insize;
    struct    rledata* rled;
};

/* Read one code from input. If EOF, return -1. */
static int UCompgetcode(struct zfile* in, struct UCompData* cd)
{
    int code, r_off, bits;
    byte* bp = cd->buf;

    if (cd->clear_flg > 0 || cd->offset >= cd->size || cd->free_ent > cd->maxcode)
    {
        /*
         * If the next entry will be too big for the current code
         * size, then we must increase the size.  This implies reading
         * a new buffer full, too.
         */
        if (cd->free_ent > cd->maxcode)
        {
            if (++cd->n_bits == cd->maxbits)
                cd->maxcode = cd->maxmaxcode;   /* won't get any bigger now */
            else
                cd->maxcode = UCOMPMAXCODE(cd->n_bits);
        }
        if (cd->clear_flg > 0)
        {
            cd->maxcode = UCOMPMAXCODE(cd->n_bits = UCOMPINIT_BITS);
            cd->clear_flg = 0;
        }

        /* This reads maximum n_bits characters into buf */
        cd->size = 0;
        while (cd->size < cd->n_bits)
        {
            int v;
            if (cd->insize == 0)
                break;
            v = zfile_getc(in);
            if (v == -1)
                break;
            cd->insize--;
            cd->buf[cd->size++] = v;
        }
        if (cd->size <= 0)
            return -1;

        cd->offset = 0;
        /* Round size down to integral number of codes */
        cd->size = (cd->size << 3) - (cd->n_bits - 1);
    }

    r_off = cd->offset;
    bits = cd->n_bits;

    /* Get to the first byte. */
    bp += (r_off >> 3);
    r_off &= 7;

    /* Get first part (low order bits) */
    code = (*bp++ >> r_off);
    bits -= (8 - r_off);
    r_off = 8 - r_off;                    /* now, offset into code word */

    /* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */
    if (bits >= 8)
    {
        code |= *bp++ << r_off;
        r_off += 8;
        bits -= 8;
    }

    /* high order bits. */
    code |= (*bp & ((1 << bits) - 1)) << r_off;
    cd->offset += cd->n_bits;

    return code;
}

static uint xadIO_Compress(struct zfile* in, struct zfile* out, int insize, struct rledata* rled, byte bitinfo)
{
    int err = 0;
    struct UCompData* cd;

    if ((bitinfo & UCOMPBIT_MASK) < UCOMPINIT_BITS)
        return XADERR_ILLEGALDATA;

    if ((cd = xcalloc(struct UCompData, 1)))
    {
        int finchar, code, oldcode, incode, blockcomp;
        byte* stackp, * stack, * stackend;

        stackp = stack = cd->stack;
        stackend = stack + UCOMPSTACKSIZE;
        cd->maxbits = bitinfo & UCOMPBIT_MASK;
        blockcomp = bitinfo & UCOMPBLOCK_MASK;
        cd->maxmaxcode = 1 << cd->maxbits;
        cd->maxcode = UCOMPMAXCODE(cd->n_bits = UCOMPINIT_BITS);
        cd->free_ent = blockcomp ? UCOMPFIRST : 256;
        cd->insize = insize;
        cd->rled = rled;

        if ((cd->tab_prefixof = xcalloc(ushort, cd->maxmaxcode)))
        {
            if ((cd->tab_suffixof = xcalloc(byte, cd->maxmaxcode)))
            {
                /* Initialize the first 256 entries in the table. */
                for (code = 255; code >= 0; code--)
                    cd->tab_suffixof[code] = code;

                if ((finchar = oldcode = UCompgetcode(in, cd)) == -1)
                    err = XADERR_DECRUNCH;
                else
                {
                    putrle(finchar, out, cd->rled);  /* first code must be 8 bits = byte */

                    while ((code = UCompgetcode(in, cd)) > -1)
                    {
                        if ((code == UCOMPCLEAR) && blockcomp)
                        {
                            for (code = 255; code >= 0; code--)
                                cd->tab_prefixof[code] = 0;
                            cd->clear_flg = 1;
                            cd->free_ent = UCOMPFIRST - 1;
                            if ((code = UCompgetcode(in, cd)) == -1)
                                break;                                /* O, untimely death! */
                        }
                        incode = code;

                        /* Special case for KwKwK string. */
                        if (code >= cd->free_ent)
                        {
                            if (code > cd->free_ent)
                            {
                                err = XADERR_ILLEGALDATA;
                                break;
                            }
                            *stackp++ = finchar;
                            code = oldcode;
                        }

                        /* Generate output characters in reverse order */
                        while (stackp < stackend && code >= 256)
                        {
                            *stackp++ = cd->tab_suffixof[code];
                            code = cd->tab_prefixof[code];
                        }
                        if (stackp >= stackend)
                        {
                            err = XADERR_ILLEGALDATA;
                            break;
                        }
                        *(stackp++) = finchar = cd->tab_suffixof[code];

                        /* And put them out in forward order */
                        do
                        {
                            putrle(*(--stackp), out, cd->rled);
                        }
                        while (stackp > stack);

                        /* Generate the new entry. */
                        if ((code = cd->free_ent) < cd->maxmaxcode)
                        {
                            cd->tab_prefixof[code] = (ushort)oldcode;
                            cd->tab_suffixof[code] = finchar;
                            cd->free_ent = code + 1;
                        }
                        /* Remember previous code. */
                        oldcode = incode;
                    }
                }
                free(cd->tab_suffixof);
            }
            else
                err = XADERR_NOMEMORY;
            free(cd->tab_prefixof);
        }
        else
            err = XADERR_NOMEMORY;
        free(cd);
    }
    else
        err = XADERR_NOMEMORY;

    return err;
}

static void MakeCRC16(ushort* buf, ushort ID)
{
    ushort i, j, k;

    for (i = 0; i < 256; ++i)
    {
        k = i;

        for (j = 0; j < 8; ++j)
        {
            if (k & 1)
                k = (k >> 1) ^ ID;
            else
                k >>= 1;
        }
        buf[i] = k;
    }
}

static ushort wrpcrc16(ushort* tab, byte* buf, int len)
{
    ushort crc = 0;
    while (len-- > 0)
        crc = tab[(crc ^ *buf++) & 0xFF] ^ (crc >> 8);
    return crc;
}

static int iswrp(byte* data)
{
    if (data[0] == 'W' && data[1] == 'a' && data[2] == 'r' && data[3] == 'p'
        && data[4] == ' ' && data[5] == 'v' && data[6] == '1' && data[7] == '.'
        && data[8] == '1' && !data[9] && !data[18] && data[19] <= 3)
        return 1;
    return 0;
}

#define COMPBUF 30000

struct zfile* unwarp(struct zfile* zf)
{
    int err = 0;
    byte buf[26] = { 0 };
    int algo, side, track;
    int pos, dstpos, olddstpos;
    ushort crc;
    uint size;
    struct zfile* nf = nullptr, * tmpf = nullptr;
    byte* zero, * data;
    int outsize = 11 * 512;
    int outsize2 = 11 * (512 + 16);
    struct rledata rled;
    ushort wrpcrc16table[256];

    MakeCRC16(wrpcrc16table, 0xa001);

    zero = xcalloc(byte, outsize2);
    olddstpos = 0;
    for (;;)
    {
        if (zfile_fread(buf, sizeof buf, 1, zf) == 0)
            break;
        if (!iswrp(buf))
            break;
        if (!nf)
        {
            nf = zfile_fopen_empty(zf, L"zipped.wrp", 1760 * 512);
            tmpf = zfile_fopen_empty(zf, L"tmp", outsize2);
        }
        track = (buf[10] << 8) | buf[11];
        algo = buf[19];
        side = -1;
        if (!memcmp(buf + 12, "BOT\0", 4))
            side = 1;
        if (!memcmp(buf + 12, "TOP\0", 4))
            side = 0;
        crc = (buf[20] << 8) | buf[21];
        pos = zfile_ftell(zf);
        dstpos = -1;
        if (side >= 0 && track >= 0 && track <= 79)
            dstpos = track * 22 * 512 + (side * 11 * 512);
        zfile_fseek(tmpf, 0, SEEK_SET);
        zfile_fwrite(zero, outsize2, 1, tmpf);
        zfile_fseek(tmpf, 0, SEEK_SET);
        size = (buf[22] << 24) | (buf[23] << 16) | (buf[24] << 8) | buf[25];
        err = 0;
        memset(&rled, 0, sizeof rled);

        switch (algo)
        {
            case 1:
                if (zfile_getc(zf) != 12)
                    err = XADERR_ILLEGALDATA;
                else
                    err = xadIO_Compress(zf, tmpf, size - 1, &rled, 12 | UCOMPBLOCK_MASK);
                break;
            case 2:
                err = ARCunsqueeze(zf, tmpf, &rled);
                break;
            case 0:
            case 3:
            {
                int i;
                for (i = 0; i < size; i++)
                {
                    byte v = zfile_getc(zf);
                    putrle(v, tmpf, algo == 3 ? &rled : nullptr);
                }
            }
            break;
            default:
                Logger::Write(L"WRP unknown compression method %d, track=%d,size=%d\n", algo, track, side);
                goto end;
                break;
        }
        if (err)
        {
            Logger::Write(L"WRP corrupt data, track=%d,side=%d,err=%d\n", track, side, err);
        }
        else
        {
            ushort crc2;
            int os = zfile_ftell(tmpf);
            data = zfile_getdata(tmpf, 0, os);
            crc2 = wrpcrc16(wrpcrc16table, data, os);
            if (crc != crc2)
                Logger::Write(L"WRP crc error %04x<>%04x, track=%d,side=%d\n", crc, crc2, track, side);
            free(data);
        }
        if (dstpos >= 0)
        {
            zfile_fseek(nf, dstpos, SEEK_SET);
            data = zfile_getdata(tmpf, 0, outsize);
            zfile_fwrite(data, outsize, 1, nf);
        }
        zfile_fseek(zf, pos + size, SEEK_SET);
    }
end:
    free(zero);
    zfile_fclose(tmpf);
    if (nf)
    {
        zfile_fclose(zf);
        zf = nf;
    }
    return zf;
}